﻿using System;
using System.Collections.Generic;
using Netbits.Common.Conditions;
using Netbits.Common.Threading;

namespace Netbits.Common.Collections
{
	/// <summary>
	/// Represents a Extended Queue
	/// </summary>
	/// <typeparam name="TItem">Type of items</typeparam>
	public class QueueExtended<TItem> : IQueueEvents<QueueExtended<TItem>, TItem>, IEnumerable<TItem>, IDisposable
	{
		#region Private Functions

		bool _Disposed;

		/// <summary>
		/// The queue
		/// </summary>
		Queue<TItem> _Queue;

		/// <summary>
		/// Should the events send asynchronous
		/// </summary>
		protected int _UseAyncEvents;
		/// <summary>
		/// Wait handler for wait if items was added to list.
		/// </summary>
		protected System.Threading.ManualResetEventSlim _WaitForItems;

		/// <summary>
		/// Fired before an item was added
		/// </summary>
		protected SaveEventHandler<QueueExtended<TItem>, TItem> _BeforeAddItem = new SaveEventHandler<QueueExtended<TItem>,TItem>();
		/// <summary>
		/// Fired after an item was added
		/// </summary>
		protected SaveEventHandler<QueueExtended<TItem>, TItem> _AfterAddItem = new SaveEventHandler<QueueExtended<TItem>,TItem>();
		/// <summary>
		/// Fired before an item was removed
		/// </summary>
		protected SaveEventHandler<QueueExtended<TItem>, TItem> _BeforeRemoveItem = new SaveEventHandler<QueueExtended<TItem>,TItem>();
		/// <summary>
		/// Fired after an item was removed
		/// </summary>
		protected SaveEventHandler<QueueExtended<TItem>, TItem> _AfterRemoveItem = new SaveEventHandler<QueueExtended<TItem>,TItem>();
		/// <summary>
		/// Fired before the list clears all items
		/// </summary>
		protected SaveEventHandler<QueueExtended<TItem>> _BeforeClearItems = new SaveEventHandler<QueueExtended<TItem>>();
		/// <summary>
		/// Fired after the list is clear
		/// </summary>
		protected SaveEventHandler<QueueExtended<TItem>> _AfterClearItems = new SaveEventHandler<QueueExtended<TItem>>();

		#endregion

		#region Constructor

		/// <summary>
		/// Create a new instance of the QueueExtended
		/// </summary>
		public QueueExtended()
		{
			_Queue = new Queue<TItem>();
			_WaitForItems = new System.Threading.ManualResetEventSlim();
		}

		/// <summary>
		/// Create a new instance of the QueueExtended copies from the specified collection.
		/// </summary>
		/// <param name="Collection_">The collection whose elements are copied to new QueueExtended</param>
		public QueueExtended( IEnumerable<TItem> Collection_ )
		{
			Condition.Requires( Collection_, "Collection_" ).IsNotNull();
			_Queue = new Queue<TItem>( Collection_ );
			_WaitForItems = new System.Threading.ManualResetEventSlim();
		}

		/// <summary>
		/// Create a new instance of the QueueExtended with specified initial capacity
		/// </summary>
		/// <param name="Capacity_">The initial numbers of items that the Queue can contain</param>
		public QueueExtended( int Capacity_ )
		{
			Condition.Requires( Capacity_, "Capacity_" ).IsGreaterThan( 0 );
			_Queue = new Queue<TItem>( Capacity_ );
			_WaitForItems = new System.Threading.ManualResetEventSlim();
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// Return the number of items in the queue
		/// </summary>
		public virtual int Count
		{
			get
			{
				return _Queue.Count;
			}
		}

		#endregion

		#region Public Functions

		/// <summary>
		/// Removes all items from the queue
		/// </summary>
		public void Clear()
		{
			__ClearAllItems();
		}

		/// <summary>
		/// Determines whether the specified item is in the list
		/// </summary>
		/// <param name="Item_">The item to check</param>
		/// <returns>true if the item is in the queue, otherwise false</returns>
		public virtual bool Contains( TItem Item_ )
		{
			Condition.Requires( Item_, "Item_ " ).IsNotNull();
			return _Queue.Contains( Item_ );
		}

		/// <summary>
		/// Removes and returns the item at the beginning of the queue
		/// </summary>
		/// <returns>The item that is removed from the beginning of the queue</returns>
		public TItem Dequeue()
		{
			return __DequeueItem();
		}

		/// <summary>
		/// Adds an item to the end of the queue
		/// </summary>
		/// <param name="Item_">The item to add to the queue.</param>
		public void Enqueue( TItem Item_ )
		{
			Condition.Requires( Item_, "Item_" ).IsNotNull();

			__EnqueueItem( Item_ );
		}

		/// <summary>
		/// Copies the items of this queue to an array, starting at a particular array index.
		/// </summary>
		/// <param name="Array_">The one-dimensional array that is the destination of the items copied from the queue</param>
		/// <param name="ArrayIndex_">The index in array at which copying begins.</param>
		public void CopyTo( TItem[] Array_, int ArrayIndex_ )
		{
			Condition.Requires( Array_, "Array_" ).IsNotNull();
			Condition.Requires( ArrayIndex_, "ArrayIndex_" ).IsInRange( 0, Array_.Length - 1 );

			TItem[] items = __ToArray();
			int itemMaxCount = Array_.Length - ArrayIndex_;
			if( itemMaxCount > items.Length )
				itemMaxCount = items.Length;

			int itemId = 0;
			for( int i = ArrayIndex_; i < itemMaxCount; i++ )
				Array_[i] = items[itemId++];
		}

		/// <summary>
		/// Returns the item at the beginning of the queue.
		/// </summary>
		/// <returns>The item at the beginning of the queue</returns>
		public TItem Peek()
		{
			return __PeekItem();
		}

		/// <summary>
		/// Copies the queue items to a new array
		/// </summary>
		/// <returns>A new array containing item copied from the queue</returns>
		public TItem[] ToArray()
		{
			return __ToArray();
		}

		/// <summary>
		/// Set the capacity to the actual number of items.
		/// </summary>
		public void TrimExcess()
		{
			__TrimExcess();
		}

		#endregion

		#region IQueueEvents

		/// <summary>
		/// Occurs before an item is added to list
		/// </summary>
		public SaveEventHandler<QueueExtended<TItem>, TItem> BeforeAddItem
		{
			get
			{
				return _BeforeAddItem;
			}
		}

		/// <summary>
		/// Occurs after an item was added to list
		/// </summary>
		public SaveEventHandler<QueueExtended<TItem>, TItem> AfterAddItem
		{
			get
			{
				return _AfterAddItem;
			}
		}

		/// <summary>
		/// Occurs before an item was removed from the list
		/// </summary>
		public SaveEventHandler<QueueExtended<TItem>, TItem> BeforeRemoveItem
		{
			get
			{
				return _BeforeRemoveItem;
			}
		}

		/// <summary>
		/// Occurs after an item was removed from list.
		/// </summary>
		public SaveEventHandler<QueueExtended<TItem>, TItem> AfterRemoveItem
		{
			get
			{
				return _AfterRemoveItem;
			}
		}

		/// <summary>
		/// Occurs before the list was cleared
		/// </summary>
		public SaveEventHandler<QueueExtended<TItem>> BeforeClearItems
		{
			get
			{
				return _BeforeClearItems;
			}
		}

		/// <summary>
		/// Occurs after the list is cleared
		/// </summary>
		public SaveEventHandler<QueueExtended<TItem>> AfterClearItems
		{
			get
			{
				return _AfterClearItems;
			}
		}

		/// <summary>
		/// Get or set if the list use asynchrony events.
		/// </summary>
		public int UseAsyncEvents
		{
			get
			{
				return _AfterAddItem.UseAsyncCallback;
			}
			set
			{
				_AfterAddItem.UseAsyncCallback = value;
				_AfterClearItems.UseAsyncCallback = value;
				_AfterRemoveItem.UseAsyncCallback = value;
				_BeforeAddItem.UseAsyncCallback = value;
				_BeforeClearItems.UseAsyncCallback = value;
				_BeforeRemoveItem.UseAsyncCallback = value;
			}
		}

		/// <summary>
		/// After this function is called the calling thread was blocked until any item is in the list
		/// </summary>
		public void WaitForItems()
		{
			_WaitForItems.Wait();
		}

		/// <summary>
		/// After this function is called the calling thread was blocked until any item is in the list or the specified timeout was reached
		/// </summary>
		/// <param name="Timeout_">The specified timeout to wait</param>
		public bool WaitForItems( int Timeout_ )
		{
			return _WaitForItems.Wait( Timeout_ );
		}

		/// <summary>
		/// Get the Wait-Handler to wait for items 
		/// </summary>
		/// <returns>The ReaderWriterLockSlim</returns>
		public System.Threading.ManualResetEventSlim WaitHandler()
		{
			return _WaitForItems;
		}

		#endregion

		#region IEnumerableExtended

		/// <summary>
		/// Get new enumerator for this queue
		/// </summary>
		/// <returns>The new enumerator</returns>
		public virtual IEnumerator<TItem> GetEnumerator()
		{
			return _Queue.GetEnumerator();
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return _Queue.GetEnumerator();
		}

		#endregion

		#region Protected Functions

		/// <summary>
		/// Clears all items from the queue
		/// </summary>
		protected virtual void __ClearAllItems()
		{
			_BeforeClearItems.Notify( this );
			_Queue.Clear();
			_AfterClearItems.Notify( this );

			_WaitForItems.Reset();
		}

		/// <summary>
		/// Removes and returns the item at the beginning of the queue
		/// </summary>
		/// <returns>The item that is removed from the beginning of the queue</returns>
		protected virtual TItem __DequeueItem()
		{
			if( Count == 0 )
				return default( TItem );

			_BeforeRemoveItem.Notify( this, __PeekItem() );
			TItem item = _Queue.Dequeue();
			_AfterRemoveItem.Notify( this, item );

			if( Count == 0 )
				_WaitForItems.Reset();

			return item;
		}

		/// <summary>
		/// Returns the item at the beginning of the queue.
		/// </summary>
		/// <returns>The item at the beginning of the queue</returns>
		protected virtual TItem __PeekItem()
		{
			if( Count == 0 )
				return default( TItem );

			return _Queue.Peek();
		}

		/// <summary>
		/// Adds an item to the end of the queue
		/// </summary>
		/// <param name="Item_">The item to add to the queue.</param>
		protected virtual void __EnqueueItem( TItem Item_ )
		{
			_BeforeAddItem.Notify( this, Item_ );
			_Queue.Enqueue( Item_ );
			_AfterAddItem.Notify( this, Item_ );
			_WaitForItems.Set();
		}

		/// <summary>
		/// Set the capacity to the actual number of items.
		/// </summary>
		protected virtual void __TrimExcess()
		{
			_Queue.TrimExcess();
		}

		/// <summary>
		/// Copies the queue items to a new array
		/// </summary>
		/// <returns>A new array containing item copied from the queue</returns>
		protected virtual TItem[] __ToArray()
		{
			return _Queue.ToArray();
		}

		/// <summary>
		/// Dispose this queue
		/// </summary>
		/// <param name="Disposing_">Dispose managed sources?</param>
		protected virtual void Dispose( bool Disposing_ )
		{
			if( !_Disposed )
			{
				if( Disposing_ )
				{
					_Queue.Clear();

					if( _WaitForItems != null )
					{
						_WaitForItems.Dispose();
						_WaitForItems = null;
					}

					if( _BeforeAddItem != null )
					{
						_BeforeAddItem.Dispose();
						_BeforeAddItem = null;
					}

					if( _AfterAddItem != null )
					{
						_AfterAddItem.Dispose();
						_AfterAddItem = null;
					}

					if( _BeforeRemoveItem != null )
					{
						_BeforeRemoveItem.Dispose();
						_BeforeRemoveItem = null;
					}

					if( _AfterRemoveItem != null )
					{
						_AfterRemoveItem.Dispose();
						_AfterRemoveItem = null;
					}

					if( _BeforeClearItems != null )
					{
						_BeforeClearItems.Dispose();
						_BeforeClearItems = null;
					}

					if( _AfterClearItems != null )
					{
						_AfterClearItems.Dispose();
						_AfterClearItems = null;
					}
				}

				_Disposed = true;
			}
		}

		#endregion

		#region Overrides

		/// <inheritdoc />
		public override bool Equals( object obj )
		{
			return _Queue.Equals( obj );
		}

		/// <inheritdoc />
		public override int GetHashCode()
		{
			return _Queue.GetHashCode();
		}

		/// <inheritdoc />
		public override string ToString()
		{
			return _Queue.ToString();
		}

		#endregion

		#region IDisposable

		/// <summary>
		/// Dispose this instance.
		/// </summary>
		public void Dispose()
		{
			Dispose( true );
			GC.SuppressFinalize( this );
		}

		#endregion
	}
}
